home *** CD-ROM | disk | FTP | other *** search
- /*
- * effects.c - Processes effects.
- *
- * (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
-
- #include "mod.h"
-
- /* Variables used for processing effects */
-
- extern struct mod_info M;
- extern struct options opt;
-
- extern int songpos, linepos, tick, speed, tempo;
- extern double mod_time, tick_time;
- extern char song_end, restart_song;
- extern char is_first_ult_effect;
-
- extern struct event *cur_event;
- extern struct voice V[MAX_VOICES];
- extern struct effects efx;
-
- /* Tables */
-
- extern short vibrato_tables[4][64];
- extern int periodtable[NR_OCTAVES*12];
-
- /* Defines */
-
- #define DOWN 1
- #define UP 2
-
- #define NORMAL 0
- #define FINE 1
-
- void check_tick0_efx(int v)
- {
- int e, e2, tmp;
- unsigned char a;
-
- /* Make sure V[v].note is set when we get a new note (and the note
- * isn't a NOTE_OFF or an argument to the toneportamento-effect). Also
- * take care of retriggering vibrato/tremolo-waveforms.
- */
- if(is_first_ult_effect && cur_event->note &&
- cur_event->note != NOTE_OFF &&
- cur_event->effect != EFX_PORTANOTE &&
- cur_event->effect2 != EFX_PORTANOTE &&
- cur_event->effect != EFX_PORTANOTEVOLSLIDE &&
- cur_event->effect2 != EFX_PORTANOTEVOLSLIDE) {
- if(V[v].real_period != V[v].period) {
- efx.set_finepitch=1;
- }
- V[v].note=cur_event->note; /* Set V[v].note */
- V[v].real_period=V[v].period=periodtable[V[v].note-BASE_NOTE];
- if(V[v].vibrato_retrig)
- V[v].vibrato_pos=0;
- if(V[v].tremolo_retrig)
- V[v].tremolo_pos=0;
- }
-
- /* Reset pitch if we have an empty event, just like PT */
- if(!cur_event->sample && !cur_event->note &&
- !cur_event->effect && !cur_event->arg &&
- !cur_event->effect2 && !cur_event->arg2) {
- if(V[v].real_period != V[v].period) {
- efx.set_finepitch=1;
- }
- V[v].real_period=V[v].period;
- }
-
- /* The following effects are only checked on the first tick */
-
- e=cur_event->effect;
- a=cur_event->arg;
- switch(e) {
- case EFX_PTSPEED:
- if(a < 32 || opt.nobpm) /* Takes care of speed-0 too */
- set_speed(a);
- else
- set_tempo(a);
- break;
- case EFX_SPEED:
- set_speed(a);
- break;
- case EFX_TEMPO:
- if(a >= 32)
- set_tempo(a); /* No tempo below 32 set */
- break;
- case EFX_BREAK:
- efx.PBreakPos=(a&0x0f)+((a>>4)&0x0f)*10;
- if(efx.PBreakPos > 63)
- efx.PBreakPos=0;
- efx.PosJumpFlag=1;
- break;
- case EFX_JUMP:
- if(a <= songpos) { /* Jump backward */
- restart_song=1; /* Make sure play.c knows song ended */
-
- if(opt.loop_module || !opt.break_loops)
- songpos=a-1; /* -1 as songpos++ is always done */
- else
- songpos=M.songlength; /* Make sure song_end gets set */
- }
- else {
- if(a <= M.songlength-1) /* Jump forward */
- songpos=a-1; /* -1 as songpos++ is always done */
- else {
- restart_song=1;
- songpos=M.songlength; /* Make sure song_end gets set */
- }
- }
-
- efx.PBreakPos=0;
- efx.PosJumpFlag=1;
- break;
-
- case EFX_VOLUME:
- V[v].volume=V[v].real_volume=MIN(a, M.volrange);
- efx.set_volume=1;
- break;
- case EFX_SAMPLEOFFSET: /* Why can't they all do it the same way :( */
- case EFX_SAMPLEOFFSET_ULT:
- case EFX_SAMPLEOFFSET_S3M:
- if(a) {
- if(e == EFX_SAMPLEOFFSET)
- V[v].last_sampleoffset=a*256;
- else if(e == EFX_SAMPLEOFFSET_S3M) {
- V[v].last_sampleoffset=a*256;
- if(M.sample[V[v].sample].looped &&
- V[v].last_sampleoffset > M.sample[V[v].sample].repeat_end) {
- /* Looplength */
- tmp=M.sample[V[v].sample].repeat_end+1-
- M.sample[V[v].sample].repeat_start;
- /* Take loop into account */
- V[v].last_sampleoffset=M.sample[V[v].sample].repeat_start+
- (V[v].last_sampleoffset-
- M.sample[V[v].sample].repeat_start)%tmp;
- }
- }
- else {
- /* Check for the '99' effect */
- if(cur_event->effect2 == EFX_SAMPLEOFFSET) {
- if(!is_first_ult_effect) /* Abort for second effect */
- break;
- V[v].last_sampleoffset=((a<<8)+cur_event->arg2)*4;
- }
- else
- V[v].last_sampleoffset=a*1024;
- }
- }
-
- if(V[v].last_sampleoffset < M.sample[V[v].sample].length)
- V[v].sampleoffset=V[v].last_sampleoffset;
- else {
- /* An invalid offset silences the voice */
- efx.kill_voice=1;
- }
- break;
-
- /* The following four effects only initialize toneportamento and
- * vibrato for the ticks to come.
- */
- case EFX_PORTANOTE:
- if(a) {
- V[v].toneporta_speed=a*4;
- }
- if(M.format == MODFORMAT_S3M)
- do_toneportamento(v);
- /* Fall through */
- case EFX_PORTANOTEVOLSLIDE:
- if(cur_event->note && cur_event->note != NOTE_OFF) {
- tmp=cur_event->note;
- tmp=MIN(tmp, opt.high_note); /* Not too high. PT: B-3 */
- tmp=MAX(tmp, opt.low_note); /* Not too low . PT: C-1 */
- V[v].period_goal=periodtable[tmp-BASE_NOTE];
- efx.dont_trigger_note=1; /* Make sure note doesn't get played */
- }
- break;
-
- case EFX_VIBRATO:
- tmp=a;
- if(tmp&0x0f)
- V[v].vibrato_amplitude=tmp&0x0f;
- if((tmp>>4)&0x0f)
- V[v].vibrato_speed=(tmp>>4)&0x0f;
- /* Fall through */
- case EFX_VIBRATOVOLSLIDE:
- /* Nothing to do here when sliding volume */
- break;
-
- /* In the protracker sources the following EFXs are checked on
- * ALL ticks, but aborts if tick != 0. So we check'em here.
- */
-
- case EFX_FINEVOLSLIDEUP:
- V[v].last_volumeslidetype=FINE;
- slide_volume(v, a, UP);
- break;
- case EFX_FINEVOLSLIDEDOWN:
- V[v].last_volumeslidetype=FINE;
- slide_volume(v, a, DOWN);
- break;
- case EFX_VOLSLIDECONTINUE:
- if(V[v].last_volumeslidetype == FINE)
- slide_volume(v, 0, 0);
- break;
- case EFX_FINEPORTAUP:
- V[v].last_portamentotype=FINE;
- do_set_portamento(v, a*4, UP);
- break;
- case EFX_FINEPORTADOWN:
- V[v].last_portamentotype=FINE;
- do_set_portamento(v, a*4, DOWN);
- break;
- case EFX_EXTRAFINEPORTAUP:
- V[v].last_portamentotype=FINE;
- do_set_portamento(v, a, UP);
- break;
- case EFX_EXTRAFINEPORTADOWN:
- V[v].last_portamentotype=FINE;
- do_set_portamento(v, a, DOWN);
- break;
- case EFX_PORTAUPCONTINUE:
- if(V[v].last_portamentotype == FINE)
- do_set_portamento(v, V[v].last_portamento, UP);
- break;
- case EFX_PORTADOWNCONTINUE:
- if(V[v].last_portamentotype == FINE)
- do_set_portamento(v, V[v].last_portamento, DOWN);
- break;
- case EFX_PATTERNDELAY:
- efx.pattern_delay=a+1; /* +1 as we should play current
- * line + arg EXTRA lines.
- */
- break;
-
- case EFX_BALANCE:
- M.panning[v]=a&0x0f;
- efx.set_balance=1;
- break;
- case EFX_VIBDEPTH: /* I _think_ this is what the ultradocs means */
- if(a&0x0f)
- V[v].vibrato_amplitude=a&0x0f;
- break;
-
- default:
- check_remaining_efx(v); /* Effects that always should be processed */
- }
-
- /* Reset real period if needed. This is a real UGLY way to do it, but
- * PT does it this way, and to make sure all cases get correct I just
- * mimic it's behaviour.
- * For ULTs we do this after the second effect has been processed.
- * For S3Ms we do it before effect2 (volume/NULL) is processed (no harm).
- */
- if(M.format != MODFORMAT_ULT || !is_first_ult_effect) {
- e2=cur_event->effect2;
- if(e<=0x0f && e!=EFX_SAMPLEOFFSET && e!=EFX_JUMP && e!=EFX_BREAK &&
- e!=EFX_PTSPEED && e!=EFX_SPEED && e!=EFX_TEMPO && e!=EFX_VOLUME &&
- e2<=0x0f && e2!=EFX_SAMPLEOFFSET && e2!=EFX_JUMP && e2!=EFX_BREAK &&
- e2!=EFX_PTSPEED && e2!=EFX_SPEED && e2!=EFX_TEMPO &&
- e2!=EFX_VOLUME) {
- if(V[v].real_period != V[v].period) {
- V[v].real_period=V[v].period;
- efx.set_finepitch=1;
- }
- }
- }
- }
-
-
- /* These effects are checked on all ticks ticks except the first
- * one on each line.
- */
-
- void check_efx(int v)
- {
- int e, e2;
- unsigned char a, tmp;
-
- e=cur_event->effect;
- a=cur_event->arg;
- switch(e) {
- case EFX_ARPEGGIO:
- if(!a)
- break;
- do_arpeggio(v, a);
- break;
-
- case EFX_PORTAUP:
- V[v].last_portamentotype=NORMAL;
- do_set_portamento(v, a*4, UP);
- break;
- case EFX_PORTADOWN:
- V[v].last_portamentotype=NORMAL;
- do_set_portamento(v, a*4, DOWN);
- break;
- case EFX_PORTAUPCONTINUE:
- if(V[v].last_portamentotype == NORMAL)
- do_set_portamento(v, V[v].last_portamento, UP);
- break;
- case EFX_PORTADOWNCONTINUE:
- if(V[v].last_portamentotype == NORMAL)
- do_set_portamento(v, V[v].last_portamento, DOWN);
- break;
- case EFX_PORTANOTEVOLSLIDE:
- V[v].last_volumeslidetype=NORMAL;
- do_volumeslide(v, a);
- case EFX_PORTANOTE:
- do_toneportamento(v);
- break;
-
- case EFX_VIBRATOVOLSLIDE:
- V[v].last_volumeslidetype=NORMAL;
- do_volumeslide(v, a);
- case EFX_VIBRATO:
- do_vibrato(v);
- break;
-
- case EFX_TREMOLO:
- do_tremolo(v);
- break;
-
- case EFX_VOLSLIDE:
- V[v].last_volumeslidetype=NORMAL;
- do_volumeslide(v, a);
- break;
- case EFX_VOLSLIDECONTINUE:
- if(V[v].last_volumeslidetype == NORMAL)
- slide_volume(v, 0, 0);
- break;
-
- case EFX_RETRIGGER:
- if(a && !(tick%a))
- efx.retrig_note=1;
- break;
- case EFX_RETRIGGERVOLSLIDE: /* S3M's retrigger */
- if(a && !(tick%(a&0x0f))) {
- efx.retrig_note=1;
- tmp=(a>>4)&0x0f;
- switch(tmp) {
- case 1: case 2: case 3: case 4: case 5:
- slide_volume(v, 1<<(tmp-1), DOWN);
- break;
- case 9: case 10: case 11: case 12: case 13:
- slide_volume(v, 1<<(tmp-9), UP);
- break;
- case 6:
- slide_volume(v, V[v].volume-V[v].volume*2/3, DOWN);
- break;
- case 7:
- slide_volume(v, V[v].volume-V[v].volume/2, DOWN);
- break;
- case 14:
- slide_volume(v, V[v].volume-V[v].volume*3/2, UP);
- break;
- case 15:
- slide_volume(v, V[v].volume-V[v].volume*2, UP);
- break;
- case 8:
- case 0:
- }
- }
- break;
-
- case EFX_TREMOR: /* As close as I get w/o more docs */
- tmp=((a>>4)&0x0f)+(a&0x0f);
- if(tmp) {
- if((tick%tmp) == ((a>>4)&0x0f))
- V[v].real_volume=0;
- else if(!(tick%tmp))
- V[v].real_volume=V[v].volume;
- if(V[v].volume != V[v].real_volume)
- efx.set_volume=1;
- }
- break;
- default:
- check_remaining_efx(v);
- }
-
- /* Reset real period if needed. This is a real UGLY way to do it, but
- * PT does it this way, and to make sure all cases get correct I just
- * mimic it's behaviour.
- * For ULTs we do this after the second effect has been processed.
- */
- if(M.format != MODFORMAT_ULT || !is_first_ult_effect) {
- e2=cur_event->effect2;
- if(e<=0x0f && e!=EFX_ARPEGGIO && e!=EFX_PORTAUP && e!=EFX_PORTADOWN &&
- e!=EFX_PORTANOTE && e!=EFX_PORTANOTEVOLSLIDE && e!=EFX_VIBRATO &&
- e!=EFX_VIBRATOVOLSLIDE &&
- e!=EFX_PORTAUPCONTINUE && e!= EFX_PORTADOWNCONTINUE &&/* S3M only */
- e2<=0x0f && e2!=EFX_ARPEGGIO && e2!=EFX_PORTAUP &&
- e2!=EFX_PORTANOTE && e2!=EFX_PORTANOTEVOLSLIDE && e2!=EFX_VIBRATO &&
- e2!=EFX_VIBRATOVOLSLIDE && e2!=EFX_PORTADOWN) {
- if(V[v].real_period != V[v].period) {
- V[v].real_period=V[v].period;
- efx.set_finepitch=1;
- }
- }
- }
- }
-
-
- /* This function checks and updates the effects that should be checked on
- * every tick.
- */
-
- void check_remaining_efx(int v)
- {
- char tmp;
- unsigned char a;
-
- a=cur_event->arg;
- switch(cur_event->effect) {
- case EFX_GLISSANDO:
- V[v].glissando=a;
- break;
- case EFX_VIBWAVEFORM:
- V[v].vibrato_waveform=a&0x03;
- V[v].vibrato_retrig=(~a)&0x04;
- break;
- case EFX_TREMWAVEFORM:
- V[v].tremolo_waveform=a&0x03;
- V[v].tremolo_retrig=(~a)&0x04;
- break;
-
- case EFX_FINETUNE:
- tmp=a;
- tmp=(tmp > 7 ? tmp|0xf0 : tmp);
- if(V[v].finetune != tmp) { /* Only set it if it's changed */
- V[v].finetune=tmp; /* Store as signed char */
- efx.set_finepitch=1;
- }
- break;
- case EFX_LOOP:
- /* Note: This EFX cannot be moved to the tick #0 block as
- * it should be processed even if we have EFX_PATTERNDELAY.
- * Not too nice, but PT does it this way...
- */
- if(tick)
- break;
-
- if(!a)
- V[v].loopstartpos=linepos-1; /* -1 as linepos++ is always done */
- else {
- if(!V[v].loopcount) {
- V[v].loopcount=a;
- efx.PBreakPos=V[v].loopstartpos;
- efx.PBreakFlag=1;
- }
- else {
- if(--V[v].loopcount) {
- efx.PBreakPos=V[v].loopstartpos;
- efx.PBreakFlag=1;
- }
- }
- }
- break;
- case EFX_NOTECUT:
- if(tick == a) {
- V[v].volume=V[v].real_volume=0;
- efx.set_volume=1;
- }
- break;
- case EFX_NOTEDELAY:
- /* A notedelay of 0 causes note not to be played at all */
- if(a) {
- if(!tick) /* Prevent note-trigger on tick #0 if arg>0 */
- efx.dont_trigger_note=1;
- else if(tick == a)
- efx.retrig_note=1; /* Make sure note gets played when arg>0 */
- }
- break;
-
- case EFX_UNUSED2:
- case EFX_FILTER: /* No sense to implement a LP-filter on a GUS */
- case EFX_INVERTEDLOOP: /* Not feasible to implement on a GUS */
- break;
-
- default: /* Do nothing for other effects */
- }
- }
-
-
- void set_speed(int s)
- {
- if(!s) { /* Speed 0 handling */
- if(opt.speed0stop) { /* stop song */
- restart_song=1;
- songpos=M.songlength;
- efx.PosJumpFlag=1; /* These needed to get song_end set */
- efx.PBreakPos=0;
- }
- }
- else
- speed=s;
- }
-
-
- void set_tempo(int t)
- {
- tempo=t;
- tick_time=250.0/tempo;
- }
-
-
- void do_arpeggio(int v, int arg)
- {
- int note;
-
- switch(tick%3) {
- case 0:
- V[v].real_period=V[v].period;
- break;
- case 1:
- note=period2note(V[v].period)+((arg>>4)&0x0f);
- /* Only change pitch if we don't go too high. PT: B-3 */
- if(note <= opt.high_note) {
- V[v].real_period=periodtable[note-BASE_NOTE];
- }
- break;
- case 2:
- note=period2note(V[v].period)+(arg&0x0f);
- /* Only change pitch if we don't go too high. PT: B-3 */
- if(note <= opt.high_note) {
- V[v].real_period=periodtable[note-BASE_NOTE];
- }
- }
- efx.set_finepitch=1;
- }
-
-
- void do_volumeslide(int v, unsigned char arg)
- {
- if(M.format != MODFORMAT_S3M) { /* PT checks UP before DOWN */
- if(arg&0xf0)
- slide_volume(v, (arg>>4)&0x0f, UP); /* Slide volume up */
- else
- slide_volume(v, arg&0x0f, DOWN); /* Slide volume down */
- }
- else { /* ST3 checks DOWN before UP */
- if(arg&0x0f)
- slide_volume(v, arg&0x0f, DOWN); /* Slide volume down */
- else
- slide_volume(v, (arg>>4)&0x0f, UP); /* Slide volume up */
- }
- }
-
- void slide_volume(int v, int amount, char dir)
- {
- if(dir == DOWN)
- amount=-amount;
-
- if(M.format == MODFORMAT_S3M) { /* S3M's use 0 as last value */
- if(!amount)
- amount=V[v].last_volumeslide;
- else
- V[v].last_volumeslide=amount;
- }
-
- V[v].volume=V[v].real_volume=MAX(MIN(M.volrange, V[v].volume+amount), 0);
- efx.set_volume=1;
- }
-
- void do_set_portamento(int v, int amount, char dir)
- {
- int per;
-
- if(M.format == MODFORMAT_S3M)
- V[v].last_portamento=amount;
-
- if(dir == UP)
- amount=-amount;
-
- per=V[v].period+amount;
-
- /* No too high or too low */
- per=MAX(per, periodtable[opt.high_note-BASE_NOTE]);
- per=MIN(per, periodtable[opt.low_note-BASE_NOTE]);
-
- V[v].period=V[v].real_period=per;
- efx.set_finepitch=1;
- }
-
-
- void do_toneportamento(int v)
- {
- if(!V[v].period_goal)
- return;
-
- if(V[v].period_goal > V[v].period) {
- V[v].period+=V[v].toneporta_speed;
- if(V[v].period >= V[v].period_goal) {
- V[v].period=V[v].period_goal;
- V[v].period_goal=0;
- }
- }
- else {
- V[v].period-=V[v].toneporta_speed;
- if(V[v].period <= V[v].period_goal) {
- V[v].period=V[v].period_goal;
- V[v].period_goal=0;
- }
- }
-
- /* Modify pitch to closest note if we have glissando on */
- if(V[v].glissando)
- V[v].real_period=periodtable[period2note(V[v].period)-BASE_NOTE];
- else
- V[v].real_period=V[v].period;
-
- efx.set_finepitch=1;
- }
-
-
- void do_vibrato(int v)
- {
- if(V[v].period < 20)
- error("ARHGH2: %d\n", V[v].period);
-
- V[v].real_period=V[v].period+(
- (vibrato_tables[V[v].vibrato_waveform][V[v].vibrato_pos/4]
- *V[v].vibrato_amplitude)/128)*4; /* /64 on old trackers */
- /* The *4 above is because X-2 => X-4 */
-
- if(V[v].real_period < 20)
- error("ARHGH: %d\n", V[v].real_period);
-
- V[v].vibrato_pos=(V[v].vibrato_pos+V[v].vibrato_speed*4)%256;
-
- efx.set_finepitch=1;
- }
-
- /* Tremolo is scaled with M.volrange */
-
- void do_tremolo(int v)
- {
- V[v].real_volume=V[v].volume+
- ((vibrato_tables[V[v].tremolo_waveform][V[v].tremolo_pos/4]
- *V[v].tremolo_amplitude)/64)*M.volrange/64;
- V[v].real_volume=MAX(MIN(V[v].real_volume, M.volrange), 0);
-
- V[v].tremolo_pos=(V[v].tremolo_pos+V[v].tremolo_speed*4)%256;
- }
-
-
- void set_pitch(int v)
- {
- int note, period, noteperiod, note_m1_period;
-
- period=V[v].real_period; /* period to bend to */
- note=period2note(period); /* "closest" note */
-
-
- noteperiod=periodtable[note-BASE_NOTE]; /* period of note */
- if(noteperiod == period) { /* Right on a note */
- V[v].pitchbend=(note-V[v].note)*100;
- return;
- }
-
- /* The following is always safe because of the above test */
- note_m1_period=periodtable[note-1-BASE_NOTE]; /* period of (note-1) */
-
- if(note < V[v].note) {
- V[v].pitchbend=(note-V[v].note)*100-
- ((period-noteperiod)*100)/(note_m1_period-noteperiod);
- }
- else {
- V[v].pitchbend=(note-V[v].note-1)*100+
- ((note_m1_period-period)*100)/
- (note_m1_period-noteperiod);
- }
- }
-